'/
' strutil.bmx - version 1.11
' Written 2010 by Anton Berlin.
' 
' =========================================================================
' 
' This program is free software; you can redistribute it and/or modify
' it under the terms of the GNU Lesser General Public License as published
' by the Free Software Foundation; either version 3 of the License.
' 
' This program is distributed in the hope that it will be useful,
' but WITHOUT ANY WARRANTY; without even the implied warranty of
' MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
' GNU General Public License for more details.
' 
' You should have received a copy of the GNU Lesser General Public License
' along with this program; if not, write to the Free Software
' Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
'/

superstrict

import BRL.Reflection

private

global DEFAULT_SEPERATOR:int = asc(" ");

public

'// Some useful string constants (79 characters for the long ones and 30 for the short ones).
const DASH_BAR:string = "-------------------------------------------------------------------------------";
const DOUBLE_DASH_BAR:string = "===============================================================================";
const DASH_BREAK:string = "------------------------------";
const DOUBLE_DASH_BREAK:string = "==============================";

'// String library. You are not supposed to create instances of this type. The reason why these functions are in a
'// type is to avoid name clashes. Think of it kindof as a namespace.
type strutil

	'// -----------------------------------------------------------------------------------------------------------------
	'// Description:
	'// 		Reverse a string.
	'// 
	'// Parameters:
	'//		(string) src: The string to reverse.
	'// -----------------------------------------------------------------------------------------------------------------
	function reverse:string(src:string)
	
		local reversedString:string;
		
		for local i:int = (src.length - 1) to 0 step -1
			reversedString :+ chr(src[i]); next
		
		return reversedString;
	
	end function
	
	'// -----------------------------------------------------------------------------------------------------------------
	'// Description:
	'// 		Length of a string (where "" returns 0 and "a" returns 1).
	'// 
	'// Parameters:
	'//		(string) src: The source string.
	'// -----------------------------------------------------------------------------------------------------------------
	function length:string(src:string)
	
		return src.length;
	
	end function
	
	'// -----------------------------------------------------------------------------------------------------------------
	'// Description:
	'// 		Repeat a string a number of times.
	'// 
	'// Parameters:
	'//		(string) src: The source string.
	'//		(int) iterations: The number of times the string should be repeated. Default is 0.
	'// -----------------------------------------------------------------------------------------------------------------
	function repeatstr:string(src:string, iterations:int = 0)
	
		local repeatedString:string;
		
		for local i:int = 1 to iterations
			repeatedString :+ src; next
		
		return repeatedString;
	
	end function
	
	'// -----------------------------------------------------------------------------------------------------------------
	'// Description:
	'// 		Return part of a string.
	'// 
	'// Parameters:
	'//		(string) src: The source string.
	'//		(int) pos: The starting position (counting from 0).
	'//		(int) length: Optional. Default value is -1. -1 means "to the end of the string). Counting from 1 (so
	'//			length = 0 would mean that you return nothing).
	'// -----------------------------------------------------------------------------------------------------------------
	function substring:string(str:string, pos:int, length:int = -1)
	
		return str[pos..length];
	
	end function
	
	'// -----------------------------------------------------------------------------------------------------------------
	'// Description:
	'// 		Strip a string from all space-characters (or a specific character of choice).
	'// 
	'// Parameters:
	'//		(string) src: The source string.
	'//		(int) char: Optional. Default value is KEY_SPACE. Ascii code of the character to strip away from the
	'//			string. If you don't specify this, it assumes you want to strip out blank spaces (spaces not tabs).
	'// -----------------------------------------------------------------------------------------------------------------
	function strip:string(src:string, char:int = -1)
	
		if (char = -1) ..
			char = DEFAULT_SEPERATOR;
		
		local returnedString:string = null;
		
		for local i:int = 0 until src.length
		
			if (src[i] <> char) ..
				returnedString :+ chr(src[i]);
		
		next
		
		return returnedString;
	
	end function
	
	'// -----------------------------------------------------------------------------------------------------------------
	'// Description:
	'// 		Strip a string from all space-characters and tabs.
	'// 
	'// Parameters:
	'//		(string) src: The source string.
	'// -----------------------------------------------------------------------------------------------------------------
	function superstrip:string(src:string)
	
		local returnedString:string = null;
		
		for local i:int = 0 until src.length
		
			if (src[i] <> $20 and src[i] <> $09) ..
				returnedString = returnedString + chr(src[i]);
		
		next
		
		return returnedString;
	
	end function
	
	'// -----------------------------------------------------------------------------------------------------------------
	'// Description:
	'// 		Returns true if the string only contains whitespaces (tabs, spaces, carriage returns and line feeds).
	'// 
	'// Parameters:
	'//		(string) src: The source string.
	'// -----------------------------------------------------------------------------------------------------------------
	function containsOnlyWhitespaces:byte(src:string)
	
		for local i:int = 0 until src.length
		
			select (src[i])
			
				case $0D, $0A, $20, $09
				default
					return false;
			
			end select
		
		next
		
		return true;
	
	end function
	
	'// -----------------------------------------------------------------------------------------------------------------
	'// Description:
	'// 		Removes all leading whitespaces from a string, and returns it.
	'// 
	'// Parameters:
	'//		(string) src: The source string.
	'// -----------------------------------------------------------------------------------------------------------------
	function stripLeadingWhitespaces:string(src:string)
	
		local strippedString:string;
		local character:int;
		local foundText:byte = false;
		
		for local i:int = 0 until src.length
		
			character = src[i];
			
			if (character = $20 or character = $09) then
			
				if (foundText) ..
					strippedString :+ chr(character);
			
			else
			
				if (not foundText) ..
					foundText = true;
				
				strippedString :+ chr(character);
			
			end if
		
		next
		
		return strippedString;
	
	end function
	
	'// -----------------------------------------------------------------------------------------------------------------
	'// Description:
	'// 		Count the words in a string, separating words using a separator character. The default separator character
	'// 		is the space character.
	'// 
	'// Parameters:
	'//		(string) src: The source string.
	'//		(byte) wordSeparator: The seperator character to use to seperate words.
	'// -----------------------------------------------------------------------------------------------------------------
	function words:int(src:string, wordSeparator:int = -1)
	
		if (wordSeparator = -1) ..
			wordSeparator = DEFAULT_SEPERATOR;
		
		local temp:string;
		local count:int = 0;
		
		if (src.length >= 1) ..
			count = 1;
		
		for local index:int = 0 until src.length
		
			if (src[index] <> wordSeparator) then
			
				temp :+ chr(src[index]);
			
			else if (temp) then
			
				count :+ 1;
				temp = null;
			
			end if
		
		next
		
		return count;
	
	end function
	
	'// -----------------------------------------------------------------------------------------------------------------
	'// Description:
	'// 		Extract a word from a string, separating words using a separator character. The default separator character
	'// 		is the space character.
	'// 
	'// Parameters:
	'//		(short) whichWord: The word to extract.
	'//		(string) src: The source string.
	'//		(byte) wordSeparator: The seperator character to use to seperate words.
	'// -----------------------------------------------------------------------------------------------------------------
	function word:string(whichWord:short, src:string, wordSeparator:int = -1)
	
		if (wordSeparator = -1) ..
			wordSeparator = DEFAULT_SEPERATOR;
		
		local result:string;
		local count:int = 0;
		
		for local index:int = 0 until src.length
		
			if (src[index] <> wordSeparator) then
			
				result :+ chr(src[index]);
			
			else if (result) then
			
				if (count <> whichWord) then
				
					count :+ 1;
					result = null;
			
				else if (count = whichWord) then
			
					exit;
				
				end if
			
			end if
		
		next
		
		return result;
	
	end function
	
	'// -----------------------------------------------------------------------------------------------------------------
	'// Description:
	'// 		Extract a word (using all whitespaces as separators - including newlines) from a string.
	'// 
	'// Parameters:
	'//		(short) whichWord: The word to extract.
	'//		(string) source: The source string.
	'// -----------------------------------------------------------------------------------------------------------------
	function wsword:string(whichWord:short, src:string)
	
		local result:string;
		local count:int = 0;
		
		for local index:int = 0 until src.length
		
			if (src[index] <> 32 ..
			and src[index] <> 9 ..
			and src[index] <> 13 ..
			and src[index] <> 10) then
			
				result :+ chr(src[index]);
			
			else if (result) then
			
				if (count <> whichWord) then
				
					count :+ 1;
					result = null;
				
				else if (count = whichWord) then
				
					exit;
				
				end if
			
			end if
		
		next
		
		return result;
	
	end function
	
	'// -----------------------------------------------------------------------------------------------------------------
	'// Description:
	'// 		Count the words (using all whitespaces as separators - including newlines) in a string.
	'// 
	'// Parameters:
	'//		(string) src: The source string.
	'// -----------------------------------------------------------------------------------------------------------------
	function wswords:int(src:string)
	
		local temp:string;
		local count:int = 0;
		
		for local index:int = 0 until src.length
		
			if (src[index] <> 32 ..
			and src[index] <> 9 ..
			and src[index] <> 13 ..
			and src[index] <> 10) then
			
				temp :+ chr(src[index]);
			
			else if (temp) then
			
				count :+ 1;
				temp = null;
			
			end if
		
		next
		
		return count;
	
	end function
	
	'// -----------------------------------------------------------------------------------------------------------------
	'// Description:
	'// 		Split all words (using all whitespaces as seperators).
	'// 
	'// Parameters:
	'//		(short) whichWord: The word to extract.
	'//		(string) source: The source string.
	'// -----------------------------------------------------------------------------------------------------------------
	function wssplit:string[](src:string)
	
		local result:string[0];
		local temp:string;
		local count:int = 0;
		
		for local index:int = 0 until src.length
		
			if (src[index] <> 32 ..
			and src[index] <> 9 ..
			and src[index] <> 13 ..
			and src[index] <> 10) then
			
				temp :+ chr(src[index]);
			
			else if (temp) then
			
				result = result[..result.length+1];
				result[count] = temp;
				
				count :+ 1;
				
				temp = null;
			
			end if
		
		next
		
		if (temp) then
		
			result = result[..result.length+1];
			result[result.length-1] = temp;
		
		end if
		
		if (not result) ..
			result = result[..1];
		
		return result;
	
	end function
	
	'// -----------------------------------------------------------------------------------------------------------------
	'// Description:
	'// 		Strips all extra whitespaces (tabs and spaces, not line feeds or carriage returns).
	'// 		Turn 'skipStrippingInQuotes' to true if you don't want to strip extra whitespaces inside of strings.
	'// 
	'// Parameters:
	'//		(string) src: The source string.
	'// -----------------------------------------------------------------------------------------------------------------
	function stripExtraWhitespaces:string(src:string, skipStrippingInQuotes:byte = false)
	
		local result:string;
		local temp:string;
		
		local insideString:byte = false;
		local insideChar:byte = false;
		
		for local index:int = 0 until src.length
		
			if (src[index] = 34 and not insideChar) then
			
				if (not insideString) ..
					insideString = true; ..
				else ..
					insideString = false;
				
				temp :+ chr(src[index]);
			
			else if (src[index] = 39 and not insideString) then
			
				if (not insideChar) ..
					insideChar = true; ..
				else ..
					insideChar = false;
				
				temp :+ chr(src[index]);
			
			else if ((src[index] <> 32 ..
			and src[index] <> 9 ..
			and src[index] <> 13 ..
			and src[index] <> 10) ..
			or insideString ..
			or insideChar) then
			
				temp :+ chr(src[index]);
			
			else if (temp) then
			
				result :+ temp + " ";
				temp = null;
			
			end if
		
		next
		
		if (temp) ..
			result :+ temp + " ";
		
		result = result[..result.length-1];
		
		return result;
	
	end function
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Description:
	'// 		Split a string into tokens, seperating tokens by spaces and tabs, and saves strings as single tokens.
	'// 		Returns an object array, so you have to cast each item in the array to a string, to use this.
	'// 
	'// Parameters:
	'//		(string) src: The source string.
	'// 		(byte) include_quotes_in_strings: Set this to true to include quotes in string tokens.
	'// 			This defaults to 'false'.
	'// -------------------------------------------------------------------------------------------------------------
	function splitTokens:object[](src:string, include_quotes_in_strings:byte = false)
	
		if (not src) ..
			return null;
		
		src :+ " ";
		
		local insideString:byte = false;
		local token:string;
		local tokenList:TList = new TList;
		
		for local index:int = 0 until src.length
		
			if (src[index] = 34) then
			
				if (insideString) then
				
					insideString = false;
					
					if (include_quotes_in_strings) ..
						token :+ chr(src[index]);
					
					tokenList.addLast(token);
					token = null;
				
				else
				
					insideString = true;
					
					if (include_quotes_in_strings) ..
						token :+ chr(src[index]);
				
				end if
			
			else if (src[index] = 32 or src[index] = 9) then
			
				if (insideString) then
				
					token :+ chr(src[index]);
				
				else
				
					if (token) then
					
						tokenList.addLast(token);
						token = null;
					
					end if
				
				end if
			
			else
			
				token :+ chr(src[index]);
			
			end if
		
		next
		
		return tokenList.toArray();
	
	end function
	
	'// -----------------------------------------------------------------------------------------------------------------
	'// Description:
	'// 		Capitalize a string.
	'// 
	'// Parameters:
	'//		(string) src: The source string.
	'// -----------------------------------------------------------------------------------------------------------------
	function cap:string(src:string)
	
		return chr(src[0]).toUpper() + src[1..];
	
	end function
	
	'// -----------------------------------------------------------------------------------------------------------------
	'// Description:
	'// 		Prevent this type to be instantiated.
	'// -----------------------------------------------------------------------------------------------------------------
	method new()
	
		local id:string = TTypeId.forObject(self).name();
		runtimeError("Illegal to create an instance of a namespace class '" + id + "'.");
	
	end method

end type
